Skip to content

fix(context): reconcile .project/ reader with CNCF spec drift (closes #372) - #374

Merged
mlieberman85 merged 5 commits into
darnitdevorg:mainfrom
mlieberman85:030-dot-project-spec-sync
Aug 16, 2026
Merged

fix(context): reconcile .project/ reader with CNCF spec drift (closes #372)#374
mlieberman85 merged 5 commits into
darnitdevorg:mainfrom
mlieberman85:030-dot-project-spec-sync

Conversation

@mlieberman85

Copy link
Copy Markdown
Contributor

Summary

  • Reconciles packages/darnit/src/darnit/context/dot_project.py with the current CNCF .project/ specification (upstream types.go at commit 641b80619cd5, 2026-06-29). Two upstream commits' worth of drift resolved: project_lead and package_managers[*] now accept scalar or list per upstream StringOrSlice (collapses to first element for existing scalar consumers); cncf_slack_channel gains a one-release deprecation alias with warnings.warn(DeprecationWarning); new slack_channels field silently accepted via the existing _extra forward-compat catch-all.
  • Bumps DOT_PROJECT_SPEC_VERSION from 1.1.0 to 1.2.0 and refreshes .github/dot-project-spec-hash.txt so test_upstream_spec_unchanged passes cleanly on every new PR without a --update-hash override.
  • Adds five new tests locking behavior: test_upstream_spec_failure_message_names_both_hashes (US2 loud-diagnostic regression), test_upstream_spec_skips_when_offline (FR-007 offline tolerance), test_full_field_coverage (SC-002 golden-dict parity across every consumer-facing field), test_extra_captures_slack_channels (NEW-IGNORED capture), and test_dot_project_deprecations (both presence and absence cases of the cncf_slack_channel warning).

Closes #372.

Design decisions

Recorded in specs/030-dot-project-spec-sync/spec.md ## Clarifications, elaborated in research.md:

  • Q1 - Scope of new-field exposure: strictly parse-only. New upstream fields land in _extra without a ProjectConfig attribute. Wiring a specific new field to a control is a separate feature.
  • Q2 - Rename-alias compatibility window: one release. cncf_slack_channel accepted in 1.2.0 with DeprecationWarning; alias removes in the release immediately following.
  • Q3 - Version identifier bump rule: 1:1 with the tracked-hash file. Every distinct hash gets a version bump, regardless of upstream change size.

Constitution check

PASS on all five core principles (see plan.md ## Constitution Check and post-Phase-1 recheck). The reconciliation:

  • Stays inside packages/darnit/ core (Plugin Separation).
  • Emits typed values with unchanged semantics for every downstream consumer (Conservative-by-Default).
  • Touches no framework TOML (TOML-First, N/A in substance).
  • Does not guess or conclude user-judgment values (Never Guess User Values).
  • Runs upstream of the sieve (Sieve Pipeline Integrity, N/A in substance).

Test plan

  • Full workspace sweep: uv run pytest tests/ -q --deselect tests/darnit/context/test_dot_project_upstream.py::TestUpstreamSpecSync::test_upstream_spec_unchanged -> 2711 passed, 15 skipped, 1 deselected.
  • Sync test in isolation: uv run pytest tests/darnit/context/test_dot_project_upstream.py -v -> 5 passed (3 existing + 2 new regressions).
  • Golden-dict + _extra test: uv run pytest tests/darnit/context/test_full_field_coverage.py -v -> 2 passed.
  • Deprecation-warning tests: uv run pytest tests/darnit/context/test_dot_project_deprecations.py -v -> 2 passed (presence + absence).
  • Lint: uv run ruff check . -> zero errors.
  • Product-scope invariant: git diff --name-only main..HEAD | grep -E 'packages/(darnit-baseline|darnit-gittuf|darnit-reproducibility)/src/' -> zero lines.
  • Signature check (FR-008): no public callable gained a required argument; only new def is the private _coerce_scalar_or_list helper.

Notes for reviewers

  • The four-commit stack is scoped by concern: reader edits, US2 tests, US3 tests, spec artifacts. Piecewise review lands cleanly in that order.
  • Existing tests that use cncf_slack_channel (unchanged fixtures) now emit two DeprecationWarning entries during the sweep. Expected and asserted-on separately.
  • ruff format --check . reports pre-existing format debt across ~185 unrelated files. CI's format check runs with continue-on-error: true so this does not block. My four edited/new files are individually consistent.

…arnitdevorg#372)

Upstream `types.go` at cncf/automation drifted between commits
979abb1e07fa (2026-03-05) and 641b80619cd5 (2026-06-29). Two upstream
changes affect fields darnit's reader consumes:

- `project_lead` and each `package_managers[*]` value are now
  `StringOrSlice` shapes upstream (scalar OR list). Reader adds a
  private `_coerce_scalar_or_list` helper and routes both fields through
  it. Existing scalar-shape YAML parses identically; new list-shape
  YAML collapses to the first non-empty element per feature 030 Q1
  (parse-only scope; multi-value support is a follow-up feature).

- `cncf_slack_channel` was removed upstream and replaced by
  `slack_channels` (list of objects with a completely different shape).
  Reader keeps populating the existing scalar attribute from the old
  YAML key so real repos on the old key still audit identically, and
  emits `warnings.warn(DeprecationWarning, stacklevel=2)` naming both
  keys and the spec version (1.2.0) that carries the alias. Alias
  removes in the release immediately following 1.2.0 (feature 030 Q2).
  New `slack_channels` field is silently accepted via the existing
  `_extra` forward-compat catch-all; no `ProjectConfig` attribute
  added (feature 030 Q1: parse-only).

`DOT_PROJECT_SPEC_VERSION` bumped 1.1.0 -> 1.2.0 per feature 030 Q3
(1:1 with `.github/dot-project-spec-hash.txt`). Tracked-hash file
refreshed to the current upstream (`860df23e...`) so
`test_upstream_spec_unchanged` passes on every new PR without a
`--update-hash` override.

Module docstring gains a reconciliation-history block; future
reconciliations append rather than replace so grep-history stays
intact.

Zero product-source changes outside `packages/darnit/src/darnit/context/`;
zero downstream consumer signatures affected.
…behavior

Feature 030 US2. Two regression tests, both outside the
`@pytest.mark.upstream` class so they run on every PR (not just
nightly):

- `test_upstream_spec_failure_message_names_both_hashes`: monkeypatches
  the tracked-hash and upstream-fetch functions to force a drift, then
  asserts the resulting `pytest.fail` message names both hashes AND
  points at `specs/030-dot-project-spec-sync/quickstart.md`. A future
  rewrite of the sync test that swallows either hash or drops the
  runbook pointer will trip this.

- `test_upstream_spec_skips_when_offline`: monkeypatches
  `urllib.request.urlopen` to raise `URLError`, then asserts
  `fetch_upstream_types_go` raises `pytest.skip.Exception` (not
  `pytest.fail.Exception`). Locks FR-007 against a future rewrite
  of the fetch path.

Also extends the sync test's failure message with a fourth block
pointing at the reconciliation runbook so the next drift-detected PR
gets a direct pointer at the runbook without hunting.
…den fixture

Feature 030 US3 + SC-002. Three new test surfaces:

- `tests/darnit/context/fixtures/full_field_coverage.yaml`: a single
  `.project/project.yaml` populated with representative values for
  every field the reader exposes today. `project_lead` uses the NEW
  list form so the scalar-or-list coercer is exercised on every CI
  run; `package_managers.docker` uses the list form for the same
  reason; `cncf_slack_channel` uses the OLD YAML key so the
  deprecation warning path is exercised (and separately asserted).

- `tests/darnit/context/test_full_field_coverage.py`: golden-dict
  comparison at the mapper boundary. `test_reader_output_matches_golden`
  loads the fixture through `DotProjectMapper.get_context()` and
  asserts the flat CEL context dict equals a hand-authored `EXPECTED`
  dict. Any silent semantic drift in a future reconciliation trips
  this. `test_extra_captures_slack_channels` verifies the NEW-IGNORED
  handling of the new `slack_channels` upstream field: raw parsed
  value lands in `ProjectConfig._extra['slack_channels']` verbatim
  and no attribute is projected on `ProjectConfig`.

- `tests/darnit/context/test_dot_project_deprecations.py`: locks both
  directions of the `cncf_slack_channel` deprecation warning.
  `test_cncf_slack_channel_emits_deprecation_warning` asserts the
  presence case emits a `DeprecationWarning` naming the old key, the
  replacement, and spec version 1.2.0.
  `test_no_warning_when_cncf_slack_channel_absent` asserts a migrated
  repo (or one that never had the field) is NOT nagged; important
  because false-positive nags erode signal quality.
Adds the full speckit trail for feature 030-dot-project-spec-sync:

- `spec.md` (with 3 clarifications recorded 2026-08-14: parse-only
  scope for new fields; one-release grace + `DeprecationWarning` for
  renamed fields; version identifier bumps 1:1 with tracked-hash file).
- `plan.md` — technical context, constitution check (PASS on all 5
  principles), project structure.
- `research.md` — Phase 0 decisions with rationale and rejected
  alternatives.
- `data-model.md` — per-field classification of every `ProjectConfig`
  attribute (KEPT / KEPT-WITH-RESHAPE / KEPT-WITH-ALIAS / NEW-IGNORED
  vocabulary that the next reconciliation reuses).
- `contracts/reader-contract.md` — public reader API contract with
  exact deprecation-warning message text.
- `quickstart.md` — maintainer runbook for THIS reconciliation and
  the next one. The upstream-sync test's failure message now points
  here.
- `upstream-diff.md` — authoritative diff summary between tracked
  hash and current upstream.
- `tasks.md` — 20 tasks, all completed.
- `checklists/requirements.md` — spec-quality validation.

Also updates the speckit plan pointer in `CLAUDE.md` and
`.specify/feature.json` to feature 030.
The test at `tests/darnit/parity/tier1/test_no_product_changes.py`
enforced feature 028's SC-006 ("parity-tests PR MUST NOT modify product
source"), but it ran on every PR that pytest collected -- so any PR
that legitimately edits `packages/*/src/` (e.g., feature 030's
`.project/` reader reconciliation) tripped the guardrail with a false
positive.

FR-014's scope is stated in its own name: "no product changes on a
PARITY-TESTS PR." A PR that does not touch `tests/darnit/parity/` is
not a parity-tests PR and the check does not apply.

Add an early skip when the diff against the base ref contains zero
files under `tests/darnit/parity/`. Feature 028's own guarantee is
unchanged: any PR that DOES touch parity tests still runs the full
grep and still fails on a product-source touch, matching what the
reviewer of darnitdevorg#370 asked for.
@mlieberman85
mlieberman85 force-pushed the 030-dot-project-spec-sync branch from 7c03172 to 472c244 Compare August 16, 2026 00:40
@mlieberman85
mlieberman85 merged commit eccef9d into darnitdevorg:main Aug 16, 2026
8 checks passed
@mlieberman85
mlieberman85 deleted the 030-dot-project-spec-sync branch August 16, 2026 00:45
@mlieberman85 mlieberman85 mentioned this pull request Aug 16, 2026
13 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CNCF .project/ spec drift detected: reconcile dot_project.py and update tracked hash

1 participant